RecyclerView原理解析

转载请注明:https://blog.csdn.net/feather_wch/article/details/79932523

参考和学习资料

  1. 自定义LayoutManager

  2. RecyclerView绘制流程解析

  3. LinearLayoutManager填充、测量、布局过程

  4. 掌握自定义LayoutManager(一) 系列开篇 常见误区、问题、注意事项,常用API

  5. 把RecyclerView撸成 马 蜂 窝

  6. 深入了解RecyclerView预布局状态(preLayout)

  7. preLayout\postLayout

1、LayoutManager的作用

  1. layout(布局)子视图
  2. 滚动过程中根据子视图布局中所处的位置,决定何时添加和回收子视图
  3. 滚动子视图。

2、RecyclerView与LayoutManager的联系

  1. RV设置LayoutManager会调用requestLayout()进行View树的重绘,最终会走RV的测量、布局、绘制三大流程。
  2. RV子View的测量、布局最终都在布局阶段交由LayoutManageronLayoutChildren完成

3、LinearLayoutManager的主要方法

方法解释
onLayoutChildrenLayoutManager的主入口,在初始化布局时调用。如果Adapter数据改变或者Adapter被替换时,会再次调用。作用:初始化时放置Item,直至填满布局为止。
canScrollHorizontally() & canScrollVertically()当想要滚动的方向,与当前布局方向相同时,在对应方法返回true,另一个返回false。
scrollHorizontallyBy() & scrollVerticallyBy()RecyclerView已经处理了触摸事件,会将相应的偏移值(dx/dy)传入这两个方法中的对应方法。根据偏移值需要完成三件事:1、将所有Item视图移动到适当位置 2、决定移动视图后 添加/移除 视图。 3、返回滚动的实际距离。框架会根据它判断你是否触碰到边界。

4、LinearLayoutManager的滚动方法源码分析。


    /**==============================================
     * RecyclerView的三种状态: 1-开始;2-布局;3-动画
     *==============================================*/
    public static class State {
        static final int STEP_START = 1;
        static final int STEP_LAYOUT = 1 << 1;
        static final int STEP_ANIMATIONS = 1 << 2;
        //xxx
    }

    /**================================================
     * 1、是否支持水平/垂直滚动
     *  1. 如果可以水平滚动,canScrollHorizontally()就返回true
     *  2. 如果可以垂直滚动,canScrollVertically()就返回true
     * @return
     *=============================================*/
    @Override
    public boolean canScrollHorizontally() {
        return mOrientation == HORIZONTAL;
    }
    @Override
    public boolean canScrollVertically() {
        return mOrientation == VERTICAL;
    }

    /**
     * 2、处理滚动事件.
     * RecyclerView处理滚动事件后,会调用对应方向的方法,并传入dx/dy偏移值。
     */
    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            return 0;
        }
        return scrollBy(dx, recycler, state);
    }
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return 0;
        }
        return scrollBy(dy, recycler, state);
    }

    /**
     * 进行滑动
     */
    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        //1. 没有子元素直接返回
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        //2. 暂定需要“回收”
        mLayoutState.mRecycle = true;
        //3. 创建mLayoutState。并且创建mOrientationHelper
        ensureLayoutState();
        //4. dy>0-LAYOUT_END: 表示向下翻滚到末尾(或者向右翻滚到最右侧), LAYOUT_START相反
        final int layoutDirection = dy > 0 ? LinearLayoutManager.LayoutState.LAYOUT_END : LinearLayoutManager.LayoutState.LAYOUT_START;
        //5. 均用正数进行计算
        final int absDy = Math.abs(dy);
        //6. 更新布局状态
        updateLayoutState(layoutDirection, absDy, true, state);
        //7. consumed = 不添加新View情况下可以滑动的距离 + 添加View所增加的像素数
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
        if (consumed < 0) {
            return 0;
        }
        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
        mOrientationHelper.offsetChildren(-scrolled);
        mLayoutState.mLastScrollDelta = scrolled;
        //8. 实际滑动的距离
        return scrolled;
    }

    /**
     * 更新布局状态
     */
    private void updateLayoutState(int layoutDirection, int requiredSpace, boolean canUseExistingSpace, RecyclerView.State state) {
        //1-用于在View的放置数量没有限制的情况中
        mLayoutState.mInfinite = resolveIsInfinite();
        //2-用于你想要在item可见前提前布局item
        mLayoutState.mExtra = getExtraLayoutSpace(state);
        //3-滑动的方向
        mLayoutState.mLayoutDirection = layoutDirection;
        int scrollingOffset;
        if (layoutDirection == LinearLayoutManager.LayoutState.LAYOUT_END) {
            mLayoutState.mExtra += mOrientationHelper.getEndPadding();
            final View child = getChildClosestToEnd(); //方向上第一个child
            //4-遍历数据适配器的方向(遍历children的方向)
            mLayoutState.mItemDirection = mShouldReverseLayout ? LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD
                    : LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL;
            //5-当前应该从adapter中获取Item的position
            mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
            //6-布局开始位置的像素偏移(添加布局时需要通过这个确定子View的位置)
            mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
            scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
                    - mOrientationHelper.getEndAfterPadding();

        } else {
            xxx
        }
        //7-布局方向上需要填充的像素数量(并减去 (View的最右侧-View不包含爬到顶的最右侧))
        mLayoutState.mAvailable = requiredSpace;
        if (canUseExistingSpace) {
            mLayoutState.mAvailable -= scrollingOffset;
        }
        //8-在不创建新View的情况下,我们可以滚动的距离(独立于布局)
        mLayoutState.mScrollingOffset = scrollingOffset;
    }

    int fill(RecyclerView.Recycler recycler, LinearLayoutManager.LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
        // max offset we should set is mFastScroll + available
        final int start = layoutState.mAvailable;
        //1-根据布局状态回收不再显示的子View
        if (layoutState.mScrollingOffset != LinearLayoutManager.LayoutState.SCROLLING_OFFSET_NaN) {
            xxx
            recycleByLayoutState(recycler, layoutState);
        }
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LinearLayoutManager.LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        //2-(无限个Item 或者 有多余的空间) 并且 Data Adapater还有更多的Item。
        // layoutState.mExtra就是整个Recyclerview的宽度或者0,
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            //3-添加View
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            xxx
        }
        //4-添加View所增加的像素数
        return start - layoutState.mAvailable;
    }

    /**
     * 根据布局方向调用合适的回收方法
     * @param recycler
     * @param layoutState
     */
    private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (!layoutState.mRecycle || layoutState.mInfinite) {
            return;
        }
        //1-上滑到最顶端or滑动到最左端。从另一头开始回收
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
        } else {
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        }
    }

    /**
     * 从末端开始视图回收
     * @param recycler
     * @param dt
     */
    private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
        //1-RecyclerView当前可见的Item数量
        final int childCount = getChildCount();
        //2-RV末端不包含padding的位置 - 在不创建新View可以滑动的最大距离
        final int limit = mOrientationHelper.getEnd() - dt;
        //3-是否是反向遍历布局
        if (mShouldReverseLayout) {
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedStart(child) < limit
                        || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                    // stop here
                    recycleChildren(recycler, 0, i);
                    return;
                }
            }
        } else {
            //4-(正常顺序的处理情况下)遍历当前可见的ItemView, 此时左滑,因此右面的View可能要回收
            for (int i = childCount - 1; i >= 0; i--) {
                View child = getChildAt(i);
                //5-当Child View的左侧已经处于limit边界之内,将该ChildView右侧所有的View回收
                if (mOrientationHelper.getDecoratedStart(child) < limit
                        || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                    //[childCount - 1, i) 内部的全部要回收
                    recycleChildren(recycler, childCount - 1, i);
                    return;
                }
            }
        }
    }
    //LinearLayoutManager.java 回收[startIndex, endIndex)
    private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        xxx
        for (int i = startIndex; i > endIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
        }
    }
    //RecyclerView.java
    public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) {
        final View view = getChildAt(index);
        //1. 移除View
        removeViewAt(index);
        //2. 将该View回收,加入到View Pool(视图池)中,用于之后的复用。
        recycler.recycleView(view);
    }
    //LinearLayoutManager.java 根据state进行布局
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                     LayoutState layoutState, LayoutChunkResult result) {
        //1-根据Position获得第一个View
        View view = layoutState.next(recycler);
        //2-没有更多Item需要布局了
        if (view == null) {
            result.mFinished = true;
            return;
        }
        //3-addView进行添加,最终是要通过Viewgroup的addView完成添加
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        //4-测量
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        ...
        //5-布局
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        ...
        result.mFocusable = view.hasFocusable();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猎羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值